#include <math.h>
#include "Box2D/Common/b2Math.h"
#include "SDL_Rect.h"
#pragma once

namespace ZeEngine
{
    template <typename T>

    class Vector2D
    {

    public: 

        Vector2D<T>() : m_X((T)0), m_Y((T)0) 
        {
        }

        Vector2D<T>(T iX, T iY)
        {
            Set(iX, iY);
        }

        Vector2D<T>(b2Vec2& other)
        {
             Set(other.x, other.y);
        }

        Vector2D<T>(const b2Vec2& other)
        {
            Set(other.x, other.y);
        }

        operator b2Vec2() const { b2Vec2 ret;  ret.x = m_X; ret.y = m_Y; return ret; }

        template <typename S>
        Vector2D<T> (const Vector2D<S>& iOther)
        {
            m_X = (T)iOther.GetX();
            m_Y = (T)iOther.GetY();
        }
           
        inline void Set(T iX, T iY)
        {
            m_X = (T)iX;
            m_Y = (T)iY;
        }

        inline void Set(const Vector2D<T>& iVector)
        {
            m_X = (T)iVector.GetX();
            m_Y = (T)iVector.GetY();
        }

        inline void SetX(T iX)
        {
            m_X = (T)iX;
        }
      
        inline void SetY(T iY)
        {
            m_Y = (T)iY;
        }

        inline T GetX() const
        {
            return m_X;
        }

        inline T GetY() const
        {
            return m_Y;
        }       

        inline void Rotate(float iAngleDeg)
        {
            const float wAngleRad = iAngleDeg * cDegToRad;

            //Rotate
            m_X = (T)(m_X * cos(wAngleRad)) - (m_Y * sin(wAngleRad));
            m_Y = (T)(m_X * sin(wAngleRad)) + (m_Y * cos(wAngleRad));  
        }

        void rotate(const Vector2D<T>& iVector, float iAngleDeg)
        {
            const T wDeltaX = iVector.GetX();
            const T wDeltaY = iVector.GetY();

            //Center to origin       
            m_X -= wDeltaX;
            m_Y -= wDeltaY;

            rotate(iAngleDeg);

            //Put it back to original position
            m_X += wDeltaX;
            m_Y += wDeltaY;
        }

        inline void Translate(const Vector2D<T>& iDeltaPosition)
        {
            m_X += iDeltaPosition.GetX();
            m_Y += iDeltaPosition.GetY();
        }

        inline Vector2D<T>& operator +=(const Vector2D<T>& iOther)
        {
            m_X += iOther.m_X;
            m_Y += iOther.m_Y;
            return (*this);
        }
        
        inline Vector2D<T>& operator -=(const Vector2D<T>& iOther)
        {
            m_X -= iOther.m_X;
            m_Y -= iOther.m_Y;
            return (*this);
        }
        
        inline Vector2D<T>& operator *=(T iValue)
        {
            m_X *= iValue;
            m_Y *= iValue;
            return (*this);
        }
        
        inline Vector2D<T>& operator /=(T iValue)
        {
            const float wMultipler = 1.0f / iValue;
            m_X = T(m_X * wMultipler);
            m_Y = T(m_Y * wMultipler);
            return (*this);
        }      
    
        inline Vector2D<T>& operator *=(const Vector2D<T>& iOther)
        {
            m_X *= iOther.m_X;
            m_Y *= iOther.m_Y;
            return (*this);
        }

        inline Vector2D<T>& operator %=(const Vector2D<T>& iOther)
        {
            m_X %= iOther.m_X;
            m_Y %= iOther.m_Y;
            return (*this);
        }

        inline Vector2D<T>& operator /=(const Vector2D<T>& iOther )
        {
            const float wMultiplerX = 1.0f / iOther.m_X;
            const float wMultiplerY = 1.0f / iOther.m_Y;
            m_X = T(m_X * wMultiplerX);
            m_Y = T(m_Y * wMultiplerY);
            return (*this);
        }   

        inline Vector2D<T> operator -(void) const
        {
            return (Vector2D<T>(-m_X, -m_Y));
        }
        
        inline Vector2D<T> operator +(const Vector2D<T>& iOther) const
        {
            return (Vector2D<T>((T)m_X + iOther.m_X, (T)m_Y + iOther.m_Y));
        }
        
        inline Vector2D<T> operator -(const Vector2D<T>& iOther) const
        {
            return (Vector2D<T>((T)m_X - iOther.m_X, (T)m_Y - iOther.m_Y));
        }
        
        inline Vector2D<T> operator *(T iValue) const
        {
            return (Vector2D<T>((T)m_X * iValue, (T)m_Y * iValue));
        }

        inline Vector2D<T> operator %(T iValue) const
        {
            return (Vector2D<T>((T)m_X % iValue, (T)m_Y % iValue));
        }

        inline Vector2D<T> operator /(T iValue) const
        {
            const float wMultiplier = (1.0f / iValue);
            return (Vector2D<T>(T(m_X * wMultiplier), T(m_Y * wMultiplier)));
        }

        inline Vector2D<T> operator *(const Vector2D<T>& iOther) const
        {
            return (Vector2D<T>((T)m_X * iOther.GetX(), (T)m_Y * iOther.GetY()));
        }

        inline Vector2D<T> operator /(const Vector2D<T>& iOther) const
        {
            const float wMultiplierX = (1.0f / iOther.GetX());
            const float wMultiplierY = (1.0f / iOther.GetY());
            return (Vector2D<T>(T(m_X * wMultiplierX), T(m_Y * wMultiplierY)));
        }
        

        inline bool operator ==(const Vector2D<T>& iOther) const
        {
            return ((m_X == iOther.m_X) && (m_Y == iOther.m_Y));
        }
        
        inline bool operator !=(const Vector2D<T>& iOther) const
        {
            return ((m_X != iOther.m_X) || (m_Y != iOther.m_Y));
        }
  
        inline float Magnitude() const
        {
            return sqrt(( (m_X * m_X) + (m_Y * m_Y) ));
        }

        inline float CrossProduct(const Vector2D& iOther) const
        {
            return (m_X * iOther.m_Y) - (m_Y * iOther.m_X);
        }

        inline float DotProduct(const Vector2D& iOther) const
        {
            return ((m_X * iOther.m_X) + (m_Y * iOther.m_Y));
        }

        inline void Normalise()
        {
            float wMagnitude = Magnitude();


            m_X /= wMagnitude;
            m_Y /= wMagnitude;
        }

     protected:
        T m_X;
        T m_Y;
    };
}
